/*
 * PINGUltrasoundSensor.java
 *
 * Created on April 25, 2008, 1:11 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package ncsutest.robowulf.sensors;

import com.sun.spot.resourcesharing.ResourceUnavailableException;
import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.io.IIOPin;
import com.sun.spot.sensorboard.peripheral.ITemperatureInput;
import java.io.IOException;

import ncsutest.robowulf.Units;

/**
 * Code to drive the PING))) Ultrasound sensor
 * Uses the EDemoBoard's temperature sensor to calculate the speed of sound.  
 * If the temperature sensor throws an exception, it is reported, but the sensor will 
 * continue to try to function, guessing that the temperature is about normal room temperature.
 * @author Douglas McClusky
 */
public class PINGUltrasoundSensor {
    private static final int MICROSECONDS_PER_SECOND = 1000000;
    private static final int MAX_TRIES = 5;
    
    private ITemperatureInput temp = EDemoBoard.getInstance().getADCTemperature();
	private IIOPin pin;
	private int units;
    
	/**
	 * @return the speed of sound, in meters per microsecond
	 */
    private double speedOfSound() {
        try {
            double tc = temp.getCelsius();
            return (331.5 + .6* tc)/MICROSECONDS_PER_SECOND;
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return 27;
    }
    
    /** Creates a new instance of PINGUltrasoundSensor 
     * @param pin - the IIOPin that the PING))) is connected to
     * @param unitOfMeasure - the public index from this class that corresponds to the desired unit of measure
     */
    public PINGUltrasoundSensor(IIOPin pin, int units) {
        this.pin = pin;
        this.units = units;
    }
    
    /**
     * There is a potential problem with this sensor if the executing thread context switches at the wrong moment.
     * While the implementing code here deals with this case, it will still fail if the VM has to switch Threads very often.
     * There are two ways to lower the probability of a poorly timed context switch:
     * <ol>
     * <li>Try to keep the number of executing Threads low.
     * <li>Synchronize your calls of this method and others that may compete with in on separate Threads.
     * </ol>
     * @return the time in milliseconds of the input pulse from the sensor.  This value is the time it takes for
     * the sensor to detect an echo.  See white paper of the sensor for details.
     * @throws ResourceUnavailableException if sensor cannot be detected
     */
    public long getRawValue() throws ResourceUnavailableException {
        //PING))):
        int tries = 0;
        long reading = 0;
        while (reading == 0) {      // This takes care of the case when the OS context switches between startPulse and getPulse
                                    // To cut down on the probability of this happening, you can make your sense ultrasound method and 
                                    // other methods that may compete with it synchronized, so they will not execute on separate threads at the same time.
        	
        	int oldP = Thread.currentThread().getPriority();
        	Thread.currentThread().setPriority(Thread.MAX_PRIORITY); // Temporarily boost thread priority to reduce chances of preemption

        	EDemoBoard.getInstance().startPulse(pin, true, 10);
            reading = EDemoBoard.getInstance().getPulse(pin, true, 25);
            
            Thread.currentThread().setPriority(oldP);
            
            if(++tries >= MAX_TRIES) {
                throw new ResourceUnavailableException("PING))) not detected on pin " + pin.getIndex());
            }
        } 
        return reading;
    }

    /**
     * There is a potential problem with this sensor if the executing thread context switches at the wrong moment.
     * While the implementing code here deals with this case, it will still fail if the VM has to switch Threads very often.
     * There are two ways to lower the probability of a poorly timed context switch:
     * <ol>
     * <li>Try to keep the number of executing Threads low.
     * <li>Synchronize your calls of this method and others that may compete with in on separate Threads.
     * </ol>
     * @return the distance of the nearest object, in the selected unit of length
     * @throws ResourceUnavailableException if sensor cannot be detected
     */
    public double getConvertedValue() throws ResourceUnavailableException {
        return (getRawValue()/2)*Units.convert(speedOfSound(), Units.METERS, units);
    }

    /**
     * There is a potential problem with this sensor if the executing thread context switches at the wrong moment.
     * While the implementing code here deals with this case, it will still fail if the VM has to switch Threads very often.
     * There are two ways to lower the probability of a poorly timed context switch:
     * <ol>
     * <li>Try to keep the number of executing Threads low.
     * <li>Synchronize your calls of this method and others that may compete with in on separate Threads.
     * </ol>
     */
     public boolean testConnection() {
        try {
        	return getRawValue() != 0;
        } catch (ResourceUnavailableException rue) {
        	return false;
        }
    }
    
}
